using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

/// <summary>
/// 滚动方向
/// </summary>
public enum ScrollDirection
{
    Horizontal = 0,
    Vertical = 1
}

/// <summary>
/// 复用滚动框
/// </summary>
[RequireComponent(typeof(ScrollRect))]
public class RScrollBox : MonoBehaviour
{
    [Header("滚动方向")] [SerializeField] private ScrollDirection m_ScrollDir;
    [Header("子项预制体")] [SerializeField] private GameObject m_ItemPrefab;
    public bool IsHorizontal => m_ScrollDir == ScrollDirection.Horizontal;

    public RectTransform m_Content;

    private ScrollRect m_ScrollRect;

    private RectTransform m_RecycleContainer;

    private HorizontalOrVerticalLayoutGroup m_LayoutGroup;

    private float m_ScrollRate;  // 滚动进度, 范围[0, 1], 0: 起始位置, 1: 结束位置
    private float m_ScrollValue;  // cotent相对于viewport的滚动值(滚出了多少)
    private int m_StartShowScrollDirIndex;  // 第一个显示的子项在滚动方向上的索引
    private int m_EndShowScrollDirIndex; // 最后一个显示的子项在滚动方向上的索引
    
    private List<object> m_ItemDataList;

    // 正在显示的子项
    private Dictionary<int, RScrollBoxItem> m_ShowItemDict;   // key: scrollDirIndex

    // PlaceHolder用于填充滚动方向上的剩余长度
    private LayoutElement m_StartPlaceHolder;
    private LayoutElement m_EndPlaceHolder;
    
    private Stack<RScrollBoxItem> m_ItemPool;  // 子项缓存池
    
    private List<float> m_CumulativeLengthList;  // 子项在滚动方向上的累积长度


    private List<object> ItemDataList
    {
        get
        {
            return m_ItemDataList;
        }

        set
        {
            m_ItemDataList = value;
            
            Reload();
        }
    }
    
    public float ScrollableLength
    {
        get
        {
            return
            IsHorizontal
                ? m_ScrollRect.content.rect.width - m_ScrollRect.viewport.rect.width
                : m_ScrollRect.content.rect.height - m_ScrollRect.viewport.rect.height;
            
        }
    }

    private void Awake()
    {
        // 容器初始化
        m_ShowItemDict = new Dictionary<int, RScrollBoxItem>();
        m_ItemPool = new Stack<RScrollBoxItem>();

        m_ScrollRect = GetComponent<ScrollRect>();

        if (m_Content == null)
        {
            Debug.LogError("Content can not is null");
            return;
        }

        // 设置ScrollRect
        m_ScrollRect.content = m_Content.transform as RectTransform;
        m_ScrollRect.viewport = transform as RectTransform;
        m_ScrollRect.onValueChanged.AddListener(OnValueChanged);

        // 设置回收层
        m_RecycleContainer = new GameObject("ReusableCells", typeof(RectTransform)).GetComponent<RectTransform>();
        m_RecycleContainer.SetParent(transform, false);
        m_RecycleContainer.gameObject.SetActive(false);

        // 创建并设置填充物
        m_StartPlaceHolder = CreatePlaceHolderItem("StartPlaceHolder");
        m_StartPlaceHolder.transform.SetParent(m_ScrollRect.content, false);
        m_EndPlaceHolder = CreatePlaceHolderItem("EndPlaceHolder");
        m_EndPlaceHolder.transform.SetParent(m_ScrollRect.content, false);

        // 检查ItemPrefab是否合法
        if (m_ItemPrefab.GetComponent<RScrollBoxItem>() == null)
        {
            Debug.LogError("ScrollBox item must have <RScrollBoxItem> component.");
        }
        
        #region 设置滚动方向限制, content锚点和检查Content是否合法
        if (IsHorizontal)
        {
            m_ScrollRect.horizontal = true;
            m_ScrollRect.vertical = false;
            m_ScrollRect.content.anchorMin = new Vector2(0f, 0f);
            m_ScrollRect.content.anchorMax = new Vector2(0f, 1f);

            HorizontalLayoutGroup layoutGroup;
            if (!m_ScrollRect.content.TryGetComponent<HorizontalLayoutGroup>(out layoutGroup))
            {
                Debug.LogError("Vertical ScrollBox content must have <HorizontalLayoutGroup> component.");
            }

            // 当childControlWidth为true时item的Preferred Width才会生效
            layoutGroup.childControlWidth = true;
            layoutGroup.childForceExpandHeight = false;
            layoutGroup.childForceExpandWidth = false;
            m_LayoutGroup = layoutGroup;
        }
        else
        {
            m_ScrollRect.horizontal = false;
            m_ScrollRect.vertical = true;
            m_ScrollRect.content.anchorMin = new Vector2(0f, 1f);
            m_ScrollRect.content.anchorMax = new Vector2(1f, 1f);

            VerticalLayoutGroup layoutGroup;
            if (!m_ScrollRect.content.TryGetComponent<VerticalLayoutGroup>(out layoutGroup))
            {
                Debug.LogError("Vertical ScrollBox content must have <VerticalLayoutGroup> component.");
            }

            layoutGroup.childControlHeight = true;
            layoutGroup.childForceExpandHeight = false;
            layoutGroup.childForceExpandWidth = false;
            m_LayoutGroup = layoutGroup;
        }
        #endregion
    }

    private void Start()
    {
        Test();
    }

    private void Test()
    {
        List<string> TestData = new List<string>(100);
        for (int i = 0; i < 100; ++i)
        {
            TestData.Add(i.ToString());
        }

        SetItemData(TestData);
    }

    private LayoutElement CreatePlaceHolderItem(string name)
    {
        GameObject go = new GameObject(name, typeof(RectTransform));
        LayoutElement le = go.AddComponent<LayoutElement>();
        return le;
    }

    public void SetItemData<T>(List<T> itemDataList)
    {
        List<object> newItemDataList = new List<object>(itemDataList.Count);
        foreach (T itemData in itemDataList)
        {
            newItemDataList.Add(itemData);
        }

        ItemDataList = newItemDataList;
    }

    /// <summary>
    /// 滚动回调, 由ScrollRect组件调用
    /// </summary>
    /// <param name="scrollVector"></param>
    private void OnValueChanged(Vector2 scrollVector)
    {
        /*
         * 竖直滚动: scrollValue.x = 1
         *          scrollValue.y = 1 在顶部
         *          scrollValue.y = 0 在底部
         */
        
        // print(scrollVector);

        #region 设置滚动值
        if (IsHorizontal)
        {
            m_ScrollRate = scrollVector.x;
        }
        else
        {
            m_ScrollRate = 1 - scrollVector.y;
        }
        m_ScrollValue = m_ScrollRate * ScrollableLength;
        #endregion
        

        RefreshShow();
        UpdatePlaceHolderLength();
    }

    /// <summary>
    /// m_ItemDataList发生变化后需要执行的操作
    /// 重新计算累积长度，回收所有Item，显示当前应显示的Item，更新PlaceHolder的长度
    /// </summary>
    private void Reload()
    {
        
        SetCumulativeLength();  // 设置累积长度
        
        #region 设置Content的大小
        
        
        // Content在设置非滚动方向的长度
        m_ScrollRect.content.offsetMin = Vector2.zero;
        m_ScrollRect.content.offsetMax = Vector2.zero;
        
        // 设置Content在滚动方向上的长度
        if (IsHorizontal)
        {
            m_ScrollRect.content.sizeDelta = new Vector2(m_CumulativeLengthList[m_CumulativeLengthList.Count - 1] + m_LayoutGroup.padding.horizontal,
                m_ScrollRect.content.sizeDelta.y);
            
            // 将位置设置到起始位置
            m_ScrollRect.content.anchoredPosition = new Vector2(m_ScrollRect.content.rect.width / 2, m_ScrollRect.content.anchoredPosition.y);
        }
        else
        {
            m_ScrollRect.content.sizeDelta = new Vector2(
                m_ScrollRect.content.sizeDelta.x,
                m_CumulativeLengthList[m_CumulativeLengthList.Count - 1] + m_LayoutGroup.padding.vertical);
            
            // 将位置设置到起始位置
            m_ScrollRect.content.anchoredPosition = new Vector2(m_ScrollRect.content.anchoredPosition.x,
                -m_ScrollRect.content.rect.height / 2);
        }
        

        #endregion

        // 计算在可见区域内的滚动方向索引范围
        SetScrollDirShowRangeIndex();
        
        ReturnAllAndRefreshShow();
        UpdatePlaceHolderLength();
    }

    private void ReturnAllAndRefreshShow()
    {
        ReturnAllItem(); // 回收所有正在显示的子项
        // 显示应该显示在可见区域中的子项
        for(int index = m_StartShowScrollDirIndex; index <= m_EndShowScrollDirIndex; ++index)
        {
            RScrollBoxItem item = GetFreeItem(index);
            item.transform.SetSiblingIndex(index - m_StartShowScrollDirIndex + 1);
            m_ShowItemDict.Add(index, item);
        }
    }

    private void RefreshShow()
    {

        int oldStart = m_StartShowScrollDirIndex;
        int oldEnd = m_EndShowScrollDirIndex;

        SetScrollDirShowRangeIndex();
        
        // print($"new start: {m_StartShowScrollDirIndex}");

        if (m_StartShowScrollDirIndex > oldEnd || m_EndShowScrollDirIndex < oldStart)
        {
            ReturnAllAndRefreshShow();
            return;
        }
        
        for (int i = oldStart; i < m_StartShowScrollDirIndex; ++i)  // 回收超出滚动框起始位置的子项
        {
            ReturnItem(i);
        }
        
        for (int i = m_EndShowScrollDirIndex + 1; i <= oldEnd; ++i)  // 回收超出滚动框末尾位置的子项
        {
            ReturnItem(i);
        }

        for (int i = oldStart - 1; i >= m_StartShowScrollDirIndex; --i)  // 添加从起始位置新移入viewport范围内的子项
        {
            AddShowItem(i, false);
            // print($"add front {i}");
        }

        for (int i = oldEnd + 1; i <= m_EndShowScrollDirIndex; ++i)  // 添加从末尾位置新移入viewport范围内的子项
        {
            AddShowItem(i, true);
            // print($"add rear {i}");
        }
    }

    private RScrollBoxItem GetFreeItem(int dataIndex)
    {
        RScrollBoxItem item;

        if (m_ItemPool.Count > 0)
        {
            item = m_ItemPool.Pop();
            item.transform.SetParent(m_Content);
        }
        else
        {
            GameObject newItemGo = Instantiate(m_ItemPrefab, m_Content);
            if (newItemGo.GetComponent<LayoutElement>() == null)
            {
                LayoutElement le = newItemGo.AddComponent<LayoutElement>();
                if (IsHorizontal)
                {
                    le.preferredWidth = m_ItemPrefab.GetComponent<RectTransform>().rect.width;
                }
                else
                {
                    le.preferredHeight = m_ItemPrefab.GetComponent<RectTransform>().rect.height;
                }
            }
            item = newItemGo.GetComponent<RScrollBoxItem>();
        }

        // 刷新显示
        item.OnRefresh(m_ItemDataList[dataIndex]);
        return item;
    }

    private void AddShowItem(int dataIndex, bool bLast=false)
    {
        if (m_ShowItemDict.ContainsKey(dataIndex))
        {
            return;
        }
        RScrollBoxItem item = GetFreeItem(dataIndex);
        m_ShowItemDict.Add(dataIndex, item);

        if (bLast)
        {
            item.transform.SetSiblingIndex(m_ScrollRect.content.childCount - 2);
        }
        else
        {
            item.transform.SetSiblingIndex(1);
        }
    }
    
    private void ReturnItem(int scrollDirIndex)
    {
        RScrollBoxItem item;
        if(m_ShowItemDict.TryGetValue(scrollDirIndex, out item))
        {
            item.OnHide();
            m_ItemPool.Push(item);
            item.transform.SetParent(m_RecycleContainer);
            m_ShowItemDict.Remove(scrollDirIndex);
        }
    }

    private void ReturnAllItem()
    {
        foreach (var item in m_ShowItemDict.Values)
        {
            item.OnHide();
            item.transform.SetParent(m_RecycleContainer);
            m_ItemPool.Push(item);
        }
        m_ShowItemDict.Clear();
    }

    /// <summary>
    /// 回收一行子项(竖直滚动)
    /// 回收一列子项(横向滚动)
    /// </summary>
    /// <param name="scrollDirIndex">滚动方向索引</param>
    private void ReturnLineItem(int scrollDirIndex)
    {
        
    }

    /// <summary>
    /// 设置滚动方向的累积长度
    /// </summary>
    private void SetCumulativeLength()
    {
        float fPaddingTop = m_LayoutGroup.padding.top;
        float fSpacing = m_LayoutGroup.spacing;
        float fItemLength;

        RectTransform rectTransform = m_ItemPrefab.GetComponent<RectTransform>();

        if (IsHorizontal)
        {
            fItemLength = rectTransform.rect.width;
        }
        else
        {
            fItemLength = rectTransform.rect.height;
        }
        
        m_CumulativeLengthList = new List<float>(m_ItemDataList.Count);
        
        for (int index = 0; index <  m_ItemDataList.Count; ++index)
        {
            if (index == 0)
            {
                m_CumulativeLengthList.Add(fPaddingTop + fItemLength);
            }
            else
            {
                m_CumulativeLengthList.Add(fSpacing + fItemLength + m_CumulativeLengthList[index - 1]);
            }
        }
    }


    /// <summary>
    /// 根据子项所在位置的坐标值获取在滚动框中滚动方向上的索引
    /// </summary>
    /// <param name="posValue">若为横向滚动模式, 则为Relative PosX
    ///若为垂直滚动模式，则为Relative PosY
    /// </param>
    /// <returns></returns>
    private int GetScrollDirIndex(float posValue)
    {
        int left = 0;
        int right = m_CumulativeLengthList.Count - 1;
        while (left <= right)
        {
            int middle = left + (right - left) / 2;
            if (posValue < m_CumulativeLengthList[middle])
            {
                right = middle - 1;
            }
            else if (posValue > m_CumulativeLengthList[middle])
            {
                left = middle + 1;
            }
            else
            {
                return middle;
            }
        }

        return left;
    }

    private void SetScrollDirShowRangeIndex()
    {
        float fStartValue;
        float fEndValue;
        
        if (IsHorizontal)
        {
            fStartValue = Math.Max(m_ScrollValue - m_LayoutGroup.padding.left, 0f);
            float fVisibleLeftPadding = Math.Max(m_LayoutGroup.padding.left - m_ScrollValue, 0);
            fEndValue = fStartValue + m_ScrollRect.viewport.rect.width - fVisibleLeftPadding;
        }
        else
        {
            fStartValue = Math.Max(m_ScrollValue - m_LayoutGroup.padding.top, 0f);
            float fVisibleTopPadding = Math.Max(m_LayoutGroup.padding.top - m_ScrollValue, 0f);
            fEndValue = fStartValue + m_ScrollRect.viewport.rect.height - fVisibleTopPadding;
        }
        m_StartShowScrollDirIndex = GetScrollDirIndex(fStartValue);
        m_EndShowScrollDirIndex = GetScrollDirIndex(fEndValue);
        
        // print($"start: {m_StartShowScrollDirIndex}, end: {m_EndShowScrollDirIndex}");
        
        if (m_StartShowScrollDirIndex >= m_ItemDataList.Count)
        {
            m_StartShowScrollDirIndex = m_ItemDataList.Count - 1;
        }

        if (m_EndShowScrollDirIndex >= m_ItemDataList.Count)
        {
            m_EndShowScrollDirIndex = m_ItemDataList.Count - 1;
        }
    }

    private void UpdatePlaceHolderLength()
    {
        if (IsHorizontal)
        {
            float fStartHiddenLength = m_StartShowScrollDirIndex == 0 ? 0 : m_CumulativeLengthList[m_StartShowScrollDirIndex - 1] - m_LayoutGroup.padding.left;
            m_StartPlaceHolder.preferredWidth = fStartHiddenLength;
            m_StartPlaceHolder.gameObject.SetActive(fStartHiddenLength > 0f);
        }
        else
        {
            float fStartHiddenLength = m_StartShowScrollDirIndex == 0 ? 0 : m_CumulativeLengthList[m_StartShowScrollDirIndex - 1] - m_LayoutGroup.padding.top;
            m_StartPlaceHolder.preferredHeight = fStartHiddenLength;
            m_StartPlaceHolder.gameObject.SetActive(fStartHiddenLength > 0f);
            
        }
        
        // m_GridLayoutGroup.padding. = m_StartShowScrollDirIndex == 0 ? 0 : m_CumulativeLengthList[m_StartShowScrollDirIndex - 1] - m_GridLayoutGroup.padding.top;
        
        // float fBottomHiddenLength = m_ScrollRect.content.rect.height - m_CumulativeLengthList[m_EndShowScrollDirIndex]
    }
}
